
import javax.swing.*; 
import javax.swing.event.*; 
import javax.swing.border.*;
import javax.swing.plaf.*;

import java.awt.*;
import java.awt.event.*;

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;


/**
 * A simple panel that arranges its children into a single row of
 * "fields".  StatusPanels are commonly used to display a set of
 * transient information at the bottom of an application.  A good
 * example of this is the Netscape Navigator web browser.  At the
 * bottom of its' main window there's a status panel with fields that
 * display information about the current connection and in some
 * versions of Navigator there's even a common operations toolbar 
 * in the rightmost field.  This class makes it easy to construct
 * status panels like the one used in Navigator.
 * <p>
 * To create a StatusPanel with two fields called "mode" and
 * "status" one would write:
 * <p>
 
 */
public class StatusPanel extends JPanel
{
    /* Field has a raised border.
     * @see Field
     */
    public final static int RAISED = 0;

    /* Field has a lowered border.
     * @see Field
     */
    public final static int LOWERED = 1;

    /* Field has a flat border.
     * @see Field
     */
    public final static int FLAT = 2;


    private final static Border panelBorder = new PanelBorder();
    private final static Border fieldBorder = new FieldBorder();

    private List fields = new ArrayList(4);
    private Color highlightColor;
    private Color shadowColor;
    private int fieldSeparatorWidth;


    /**
     * Create a status panel with one Field for each component.
     */
    public StatusPanel(JComponent[] fields) {
	super(new Layout());
	addFields(fields);
    }


    /**
     * Create a status panel with one JLabel Field for each value converted 
     * to a string with the Object.toString() method.
     */
    public StatusPanel(Object[] values) {
	super(new Layout());
	JComponent[] fields = new JComponent[values.length];
	for(int i = 0; i < values.length; i++) {
	    if (values[i] instanceof Icon) {
		fields[i] = new JLabel((Icon)values[i]);
	    }
	    else {
		String value = values[i].toString();
		fields[i] = new JLabel(value);
		fields[i].setName(value);
	    }
	}
	addFields(fields);
    }


    /**
     * Create an empty StatusPanel.
     */
    public StatusPanel() {
	this(new JComponent[0]);
    }


    private void addFields(JComponent[] initialFields) {
	for(int i = 0; i < initialFields.length; i++) {
	    add(initialFields[i]);
	}
    }


    /** 
     * Initialize look and feel dependent properties that have not been 
     * explicitly set.  A property is considered "not explicitly set" 
     * if it's value is an instanceof UIResource (or null).  The former
     * implies that the value came from the look and feel defaults
     * table and the latter is assumed to mean that the property
     * hasn't been initialized at all.  We initialize the border and background 
     * properthere.
     * 
     * @see javax.swing.plaf.UIResource
     * @see UIManager#getColor
     * @see UIManager#getBorder
     */
    public void updateUI() {
	super.updateUI();

	/* The shadow and background colors are based on defaults 
	 * that are defined for all look and feels .  The panelBorder 
	 * is also the same for all L&Fs.  If user has set the border
	 * or the background properties we're careful to not to reset
	 * them here.
	 */

	Color background = getBackground();
	if ((background == null) || (background instanceof UIResource)) {
	    setBackground(UIManager.getColor("control"));
	    highlightColor = UIManager.getColor("controlLtHighlight");
	    shadowColor = UIManager.getColor("controlShadow");
	}

	Border border = getBorder();
	if ((border == null) || (border instanceof UIResource)) {
	    setBorder(panelBorder);
	}

	/* The fieldSeparatorWidth property is L&F specific so 
	 * it's value is based on the current default L&F.
	 */

	LookAndFeel laf = UIManager.getLookAndFeel();
	String lafID = (laf != null) ? laf.getID() : "";

	if (lafID.startsWith("Motif")) {
	    fieldSeparatorWidth = 4;
	}
	else {  
	    fieldSeparatorWidth = 2;
	}
    }


    /** 
     * Return the field whose components index, vis getComponents(),
     * is index.
     */
    public Field getField(int index) {
	return (Field)(fields.get(index));
    }


    /** 
     * Return the Field whose component has the specified
     * name, or null if no such component exists.
     */
    public Field getField(String name) {
	for(int i = 0; i < fields.size(); i++) {
	    Field field = (Field)(fields.get(i));
	    if (field.getComponent().getName().equals(name)) {
		return field;
	    }
	}
	return null;
    }


    /** 
     * Return the Field that contains the specified component,
     * or null if no such component exists.
     */
    public Field getField(JComponent component) {
	for(int i = 0; i < fields.size(); i++) {
	    Field field = (Field)(fields.get(i));
	    if (field.getComponent() == component) {
		return field;
	    }
	}
	return null;
    }


    /** 
     * This method is only used by the inner static border classes
     * below.
     */
    private Color getHighlightColor() {
	return highlightColor;
    }


    /** 
     * This method is only used by the inner static border classes
     * below.
     */
    private Color getShadowColor() {
	return shadowColor;
    }


    /** 
     * This method is only used by the inner static border and layout
     * classes below.
     */
    private int getFieldSeparatorWidth() {
	return fieldSeparatorWidth;
    }


    /**
     * Synchronizes fields and children.  The new fields background is set 
     * to the value of this panels background.
     * 
     * @see #getField
     */
    protected void addImpl(Component c, Object constraints, int index) {
	if (c instanceof JComponent) {
	    super.addImpl(c, constraints, index);
	    c.setBackground(getBackground());
	    Field newField = new Field((JComponent)c);
	    if (index == -1) {
		fields.add(newField);
	    }
	    else {
		fields.add(index, newField);
	    }
	}
	else {
	    throw new IllegalArgumentException("not a JComponent");
	}
    }


    /**
     * Synchronizes fields and children.
     * @see #getField
     */
    public void remove(int index) {
	fields.remove(index);
	super.remove(index);
    }


    /**
     * Synchronizes fields and children.
     * @see #getField
     */
    public void removeAll() {
	fields.clear();
	super.removeAll();
    }
    

    /**
     * If the background is changed to a color that's not a UIResource,
     * i.e. it wasn't found in a Look and Feel defaults table, then 
     * recompute the shadow and highlight colors used to render borders.
     * 
     * @see #updateUI
     */
    public void setBackground(Color background) {
	super.setBackground(background);
	if (!(background instanceof UIResource)) {
	    highlightColor = background.brighter();
	    shadowColor = background.darker();
	}
    }


    /** 
     * Each StatusPanel child is associated with one Field object.  The Field 
     * object defines how the childs border is rendered and and how much space
     * is allocated to the child when the StatusPanel is resized.
     */
    public static class Field {
	private final JComponent component;
	private int borderType = LOWERED;
	private float layoutWeight = 0.0f;

	Field(JComponent component, int borderType, float layoutWeight) {
	    super();
	    if (component == null) {
		throw new IllegalArgumentException("null component");
	    }
	    this.component = component;
	    setBorderType(borderType);
	    setLayoutWeight(layoutWeight);
	    component.setBorder(fieldBorder);
	}

	Field(JComponent component) {
	    this(component, LOWERED, 0.0f);
	}
	
	public JComponent getComponent() {
	    return component;
	}

	public int getBorderType() {
	    return borderType;
	}

	public void setBorderType(int borderType) {
	    switch(borderType) {
	      case RAISED:
	      case LOWERED:
	      case FLAT:
		  this.borderType = borderType;
		  component.repaint();
		  break;
	      default:
		  throw new IllegalArgumentException("invalid borderType");
	    }
	}

	public float getLayoutWeight() {
	    return layoutWeight;
	}

	public void setLayoutWeight(float weight) {
	    this.layoutWeight = weight;
	    JComponent parent = (JComponent)(component.getParent());
	    parent.revalidate();
	    parent.repaint();
	}

	public String toString() {
	    String propertiesString = component.getName() + " ";
	    switch(getBorderType()) {
	      case RAISED: propertiesString = "border=RAISED"; break;
	      case LOWERED: propertiesString = "border=LOWERED"; break;
	      default: propertiesString = "border=FLAT";
	    }
	    if (getLayoutWeight() > 0.0f) {
		propertiesString += " layoutWeight=" + getLayoutWeight();
	    }
	    return getClass().getName() + "[" + propertiesString + "]";
	}
    }


    private static class Layout implements LayoutManager 
    {
	public void addLayoutComponent(String s, Component c) {
	}

	public void removeLayoutComponent(Component c) {
	}

	private Dimension sizeWithBorders(Container parent, int n, int w, int h) {
	    StatusPanel statusPanel = (StatusPanel)parent;
	    Insets insets = parent.getInsets();
	    int height = h + insets.top + insets.bottom;
	    int width = w + insets.left + insets.right + 
		((n == 0) ? 0 : (statusPanel.getFieldSeparatorWidth() * (n - 1)));
	    return new Dimension(width, height);
	}

	public Dimension preferredLayoutSize(Container parent) {
	    int nChildren = parent.getComponentCount();
	    int totalWidth = 0, maxHeight = 0;
	    for(int i = 0; i < nChildren; i++) {
		Component field = parent.getComponent(i);
		Dimension d = field.getPreferredSize();
		totalWidth += d.width;
		if (d.height > maxHeight) {
		    maxHeight = d.height;
		}
	    }
	    return sizeWithBorders(parent, nChildren, totalWidth, maxHeight);
	}

	public Dimension minimumLayoutSize(Container parent) {
	    int nChildren = parent.getComponentCount();
	    int totalWidth = 0, maxHeight = 0;
	    for(int i = 0; i < nChildren; i++) {
		Component field = parent.getComponent(i);
		Dimension d = field.getMinimumSize();
		totalWidth += d.width;
		if (d.height > maxHeight) {
		    maxHeight = d.height;
		}
	    }
	    return sizeWithBorders(parent, nChildren, totalWidth, maxHeight);
	}

	public void layoutContainer(Container parent) {
	    StatusPanel statusPanel = (StatusPanel)parent;
	    int nChildren = parent.getComponentCount();
	    Insets insets = parent.getInsets();
	    int[] widths = new int[nChildren];
	    int x = insets.left;
	    int y = insets.top;
	    int height = parent.getHeight() - (insets.top + insets.bottom);

	    /* Determine how much horizontal space we're going to 
	     * need to give all of their fields their preferred
	     * widths, allowing for insets and the field separators.
	     */
	    for(int i = 0; i < nChildren; i++) {
		Component child = parent.getComponent(i);
		x += widths[i] = child.getPreferredSize().width;
		if (i < (nChildren - 1)) {
		    x += statusPanel.getFieldSeparatorWidth();
		}
	    }
	    x += insets.right;

	    /* Allocate any extra horizontal space to the
	     * fields with non-zero weights. We're assuming
	     * that the caller has ensured that the sum of
	     * the field weights is 1.0.
	     */
	    int extraWidth = parent.getWidth() - x;
	    int remainingWidth = extraWidth;
	    int lastWeightedField = -1;
	    if (extraWidth > 0) {
		for(int i = 0; i < nChildren; i++) {
		    float weight = statusPanel.getField(i).getLayoutWeight();
		    if (weight > 0.0f) {
			int weightedExtraWidth = (int)(extraWidth * weight);
			widths[i] += weightedExtraWidth;
			remainingWidth -= weightedExtraWidth;
			lastWeightedField = i;
		    }
		}
	    }

	    /* Since we truncate each of the (weight * width) 
	     * values, it's possible that there will still be
	     * some width that hasn't been allocated yet.  We
	     * allocate it to the last weighted field here.
	     */
	    if ((lastWeightedField != -1) && (remainingWidth > 0)) {
		widths[lastWeightedField] += remainingWidth;
	    }

	    /* Layout the Field components  using the widths 
	     * computed above.
	     */
	    x = insets.left;
	    for(int i = 0; i < nChildren; i++) {
		Component child = parent.getComponent(i);
		child.setBounds(x, y, widths[i], height);
		x += widths[i] + statusPanel.getFieldSeparatorWidth();
	    }
	}
    }

    

    private static class PanelBorder implements Border
    {
	public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) {
	    StatusPanel statusPanel = (StatusPanel)c;

	    g.setColor(statusPanel.getHighlightColor());
	    g.drawLine(x, y, x+w-1, y);           // +-----
	    g.drawLine(x, y+1, x, y+h-2);         // |

	    g.setColor(statusPanel.getShadowColor());
	    g.drawLine(x+w-1, y, x+w-1, y+h-2);   //       |
	    g.drawLine(x, y+h-1, x+w-1, y+h-1);   // ------+
	}

	public Insets getBorderInsets(Component c) {
	    return new Insets(3, 3, 3, 3);
	}

	public boolean isBorderOpaque() {
	    return false;
	}
    }


    private static class FieldBorder implements Border 
    {
	public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) {
	    StatusPanel statusPanel = (StatusPanel)(c.getParent());
	    Field field = statusPanel.getField((JComponent)c);
	    Color upperEdgeColor, lowerEdgeColor;

	    switch(field.getBorderType()) {
	      case RAISED:
		  upperEdgeColor = statusPanel.getHighlightColor();
		  lowerEdgeColor = statusPanel.getShadowColor();
		  break;
	      case LOWERED:
		  upperEdgeColor = statusPanel.getShadowColor();
		  lowerEdgeColor = statusPanel.getHighlightColor();
		  break;
	      default:
		  upperEdgeColor = lowerEdgeColor = statusPanel.getBackground();
	    }

	    g.setColor(upperEdgeColor);
	    g.drawLine(x, y, x+w-1, y);           // +-----
	    g.drawLine(x, y+1, x, y+h-2);         // |

	    g.setColor(lowerEdgeColor);
	    g.drawLine(x+w-1, y, x+w-1, y+h-2);   //       |
	    g.drawLine(x, y+h-1, x+w-1, y+h-1);   // ------+
	}


	public Insets getBorderInsets(Component c) {
	    return new Insets(2, 2, 2, 2);
	}

	public boolean isBorderOpaque() {
	    return false;
	}
    }


    public static void main(String[] args) 
    {
	/*
	try {
	    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
	}
	catch (Exception e) {
	    e.printStackTrace();
	}
	*/

	String[] fields = {"Location", "Type", "Modifiers", "Source"};

	StatusPanel northStatusPanel = new StatusPanel(fields);
	StatusPanel centerStatusPanel = new StatusPanel(fields);
	StatusPanel southStatusPanel = new StatusPanel(fields);
	final StatusPanel[] allPanels = {
	    northStatusPanel, 
	    centerStatusPanel, 
	    southStatusPanel
	};

	northStatusPanel.getField("Source").setLayoutWeight(1.0f);
	southStatusPanel.getField("Source").setLayoutWeight(1.0f);

	centerStatusPanel.setBackground(new Color(103, 174, 106));

	/* If we're running on a Windows platform the StatusPanel looks a little
	 * better if replace the border with one that isn't beveled.  This is
	 * because the Windows top level windows borders already have a 
	 * sharp raised border.
	 */
	try {
	    String osName = System.getProperty("os.name");
	    if ((osName != null) && (osName.indexOf("Windows") != -1)) {
		northStatusPanel.setBorder(new EmptyBorder(2, 0, 2, 0));
		southStatusPanel.setBorder(new EmptyBorder(2, 0, 0, 0));
	    }
	}
	catch (SecurityException e) {
	}

	JPanel centerPanel = new JPanel();
	centerPanel.setPreferredSize(new Dimension(300, 200));
	centerPanel.setBackground(Color.white);
	centerPanel.add(centerStatusPanel);

	class MousePrinter extends MouseAdapter implements MouseMotionListener 
	{
	    private void print(String fieldName, Object value) {
		String text = value.toString();
		if (text.length() > 48) {
		    StringBuffer sb = new StringBuffer(text.substring(0, 45));
		    sb.append("...");
		    text = sb.toString();
		}
		for(int i = 0; i < allPanels.length; i++) {
		    Field f = allPanels[i].getField(fieldName);
		    JLabel l = (JLabel)(f.getComponent());
		    l.setText(text);
		}
	    }

	    private boolean isModifierPressed(MouseEvent e, int mask) {
		return (e.getModifiers() & mask) == mask;
	    }

	    private void printMouseEvent(MouseEvent e) {
		String mods = "";
		if (isModifierPressed(e, InputEvent.SHIFT_MASK)) {
		    mods += " shift";
		}
		if (isModifierPressed(e, InputEvent.CTRL_MASK)) {
		    mods += " control";
		}
		if (isModifierPressed(e, InputEvent.META_MASK)) {
		    mods += " meta";
		}
		if (isModifierPressed(e, InputEvent.ALT_MASK)) {
		    mods += " alt";
		}
		if (isModifierPressed(e, InputEvent.BUTTON1_MASK)) {
		    mods += " button1";
		}
		if (isModifierPressed(e, InputEvent.BUTTON2_MASK)) {
		    mods += " button2";
		}
		if (isModifierPressed(e, InputEvent.BUTTON3_MASK)) {
		    mods += " button3";
		}
		if (mods.length() == 0) {
		    mods = "<no modifiers>";
		}
		String type = "";
		switch (e.getID()) {
		  case MouseEvent.MOUSE_PRESSED:  type = "MOUSE_PRESSED"; break;
		  case MouseEvent.MOUSE_RELEASED: type = "MOUSE_RELEASED"; break;
		  case MouseEvent.MOUSE_CLICKED:  type = "MOUSE_CLICKED"; break;
		  case MouseEvent.MOUSE_ENTERED:  type = "MOUSE_ENTERED"; break;
		  case MouseEvent.MOUSE_EXITED:   type = "MOUSE_EXITED"; break;
		  case MouseEvent.MOUSE_MOVED:    type = "MOUSE_MOVED"; break;
		  case MouseEvent.MOUSE_DRAGGED:  type = "MOUSE_DRAGGED"; break;
  		  default: 
		      type = "<unknown>";
		}
		print("Type", type);
		print("Location", e.getPoint());
		print("Modifiers", mods.trim());
		print("Source", e.getSource());
	    }

	    private void border(String fieldName, int type) {
		for(int i = 0; i < allPanels.length; i++) {
		    Field f = allPanels[i].getField(fieldName);
		    f.setBorderType(type);
		}
	    }

	    public void mousePressed(MouseEvent e) {
		printMouseEvent(e);
		border("Type", StatusPanel.LOWERED);
	    }
	    public void mouseReleased(MouseEvent e) {
		printMouseEvent(e);
		border("Type", StatusPanel.RAISED);
	    }
	    public void mouseDragged(MouseEvent e) {
		printMouseEvent(e);
	    }
	    public void mouseMoved(MouseEvent e) {
		printMouseEvent(e);
		border("Type", StatusPanel.FLAT);
	    }
	}

	MousePrinter mp = new MousePrinter();
	centerPanel.addMouseListener(mp);
	centerPanel.addMouseMotionListener(mp);

	JFrame frame = new JFrame("Status Panel Demo");

	WindowListener l = new WindowAdapter() {
	    public void windowClosing(WindowEvent e) { 
		System.exit(0); 
	    }
	};
	frame.addWindowListener(l);
	
	Container contentPane = frame.getContentPane();
	contentPane.add(northStatusPanel, BorderLayout.NORTH);
	contentPane.add(centerPanel, BorderLayout.CENTER);
	contentPane.add(southStatusPanel, BorderLayout.SOUTH);
	frame.pack();
	frame.setVisible(true);
    }
}


